package com.ehome.poct.core.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ehome.core.page.Page;
import com.ehome.core.service.AbstractCrudServiceImpl;
import com.ehome.exception.BizCheckedException;
import com.ehome.exception.BizException;
import com.ehome.poct.base.api.dto.account.TermAgentOrgDTO;
import com.ehome.poct.base.api.dto.doctor.ClinicDTO;
import com.ehome.poct.base.api.enums.BusinessOrderTypeEnums;
import com.ehome.poct.base.api.enums.StripTypeEnums;
import com.ehome.poct.base.api.qry.device.TestDeviceQry;
import com.ehome.poct.base.api.vo.device.TestDeviceVO;
import com.ehome.poct.core.api.dto.DelReportDTO;
import com.ehome.poct.core.api.dto.ReportListDTO;
import com.ehome.poct.core.api.dto.TestDataDTO;
import com.ehome.poct.core.api.dto.WxDTO;
import com.ehome.poct.core.api.dto.check.*;
import com.ehome.poct.core.api.dto.check.gold.*;
import com.ehome.poct.core.api.dto.checkdata.CheckItemResultDTO;
import com.ehome.poct.core.api.dto.common.AgeSegment;
import com.ehome.poct.core.api.dto.common.CommonUtil;
import com.ehome.poct.core.api.dto.order.*;
import com.ehome.poct.core.api.dto.patient.PatientCategory;
import com.ehome.poct.core.api.dto.report.*;
import com.ehome.poct.core.api.virtual.ReportService;
import com.ehome.poct.core.api.virtual.TestOrderService;
import com.ehome.poct.core.api.virtual.TestReportDiseaseConfigService;
import com.ehome.poct.core.api.virtual.WxService;
import com.ehome.poct.core.api.vo.CheckReportVO;
import com.ehome.poct.core.api.vo.DxResultVO;
import com.ehome.poct.core.api.vo.ReportDetailVO;
import com.ehome.poct.core.api.vo.ReportDtlVO;
import com.ehome.poct.core.api.vo.checkdata.TestDataDetailVO;
import com.ehome.poct.core.constant.BizCode;
import com.ehome.poct.core.constant.ClinicBizCodeEnums;
import com.ehome.poct.core.constant.OrderBizCodeEnums;
import com.ehome.poct.core.constant.ReportConst;
import com.ehome.poct.core.mapping.TestReportSvcMapping;
import com.ehome.poct.core.repository.*;
import com.ehome.poct.core.repository.check.CheckItemCategoryRepository;
import com.ehome.poct.core.repository.check.TestRecordDetailExtRepository;
import com.ehome.poct.core.repository.check.TestRecordDetailRepository;
import com.ehome.poct.core.repository.checkdata.BldCheckDataRepository;
import com.ehome.poct.core.repository.order.*;
import com.ehome.poct.core.repository.report.*;
import com.ehome.poct.core.service.mapping.DataReportDetermineMapping;
import com.ehome.poct.core.service.rpc.ClinicClient;
import com.ehome.poct.core.service.rpc.DeviceClient;
import com.ehome.poct.core.service.rpc.TermAgentOrgClient;
import com.ehome.poct.core.utils.EHomeAssert;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @description: 报告相关
 * @author: muqiu
 * @time: 2022/3/3 17:34
 */
@Slf4j
@Service
public class ReportServiceImpl extends AbstractCrudServiceImpl<CheckReportDTO> implements ReportService {
    @Autowired
    private BldReportRepository bldReportRepository;
    @Autowired
    private CheckItemIndicatorRepository checkItemIndicatorRepository;
    @Autowired
    private CheckProjectRepository checkProjectRepository;
    @Autowired
    private PatientRepository patientRepository;
    @Autowired
    private TermPatientRepository termPatientRepository;
    @Autowired
    private TestReportPoctRepository testReportPoctRepository;
    @Autowired
    private TestDataDetailRepository testDataDetailRepository;
    @Autowired
    private TestConfPoctStripTypeRepository confPoctStripTypeRepository;
    @Autowired
    private TestConfPoctStripMappingRepository testConfPoctStripMappingRepository;
    @Autowired
    private TestReportPoctUniRepository testReportPoctUniRepository;
    @Autowired
    private BldCheckDataRepository bldCheckDataRepository;
    @Autowired
    private CheckItemCategoryRepository checkItemCategoryRepository;
    @Autowired
    private TestConfUniProjectDescRepository testConfUniProjectDescRepository;
    @Autowired
    private TestReportSvcMapping reportMapping;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private TestReportDiseaseConfigService diseaseConfigService;
    @Autowired
    private BloodPreciseJudeRepository preciseJudeRepository;
    @Autowired
    private DataReportDetermineMapping dataReportDetermineMapping;
    @Autowired
    private TestConfPoctAnalysisRepository testConfPoctAnalysisRepository;
    @Autowired
    private TestConfClinicalReportStandardRepository testConfClinicalReportStandardRepository;
    @Autowired
    private TestConfClinicalReportAnalyseRepository testConfClinicalReportAnalyseRepository;
    @Autowired
    private TestConfPoctUniAnalysisRepository testConfPoctUniAnalysisRepository;
    @Autowired
    private TestOrderItemRepository testOrderItemRepository;
    @Autowired
    private TestOrderRepository testOrderRepository;
    @Autowired
    private TestOrderCheckRecordRepository testOrderCheckRecordRepository;
    @Autowired
    private TestRecordDetailRepository testRecordDetailRepository;

    @Autowired
    private WxService wxService;
    @Autowired
    private TestReportDataConfigRepository dataConfigService;
    @Autowired
    private WxDTO wxProperties;
    @Value("${notify.detail.env}")
    private String enyType;

    @Autowired
    private TestOrderService testOrderService;

    @Autowired
    private ClinicClient clinicClient;

    @Autowired
    private PromotionBundlePriceProfitConfRepository promotionBundlePriceProfitConfRepository;

    @Autowired
    private DeviceClient deviceClient;

    @Autowired
    private TermAgentOrgClient termAgentOrgClient;
    @Autowired
    private TestRecordDetailExtRepository recordDetailExtRepository;
    @Autowired
    private TestOrderExtendsRepository orderExtendsRepository;

    /**
     * 联合报告配置的起步大小
     */
    private final Integer unionSize = 2;
    /**
     * 联合报告类型
     */
    private final Integer unionReportType = 2;
    /**
     * 单项报告类型
     */
    private final Integer sigReportType = 1;
    /**
     * 三联测CODE
     */
    private final Integer CRP_SAA_BLOOD = 121;

    public static final String WEXIN_TOKEN_KEY = "wexin_access_token";


    @Override
    public Page<CheckReportVO> getCheckList(ReportListDTO dto) {
        Page<CheckReportVO> checkList = bldReportRepository.getCheckList(dto);
        //患者名称
        checkList.getList().parallelStream().forEach(r -> {
            if (r.getPatientId() != null) {
                r.setPatientName(patientRepository.getPatient(r.getPatientId()).getUserName());
            }
        });
        return checkList;
    }

    @Override
    public ReportDetailVO getCheckReportByOrderNo(String orderNo) {
        ReportDetailVO reportDetailVO = bldReportRepository.getCheckReportByOrderNo(orderNo);
        if (reportDetailVO == null) {
            throw new BizCheckedException(400, "订单号不存在");
        }
        return reportDetailVO;
    }

    @Override
    public ReportDetailVO getCheckReport(Long reportId) {
        ReportDetailVO reportDetailVO = bldReportRepository.getCheckReport(reportId);
        if (reportDetailVO == null) {
            throw new BizCheckedException(400, "报告id不存在");
        }
        return reportDetailVO;
    }

    @Override
    public void delJby(DelReportDTO delReportDTO) {
        if (delReportDTO.getReportType() == 1) {
            testReportPoctRepository.delete(delReportDTO.getReportId());
        } else if (delReportDTO.getReportType() == 2) {
            testReportPoctUniRepository.delete(delReportDTO.getReportId());
        }
    }

    @Override
    public void delBld(Long id) {
        bldReportRepository.delBld(id);
    }

    @Override
    public ReportDtlVO getSingleOne(Long id) {
        // 1.1 获取报告
        var jbyReport = testReportPoctRepository.getById(id);
        return Optional.ofNullable(jbyReport).map(r -> {
            // 1.2 获取检测数据
            TestDataDTO data = this.getDetail(r.getTestPoctDataId());
            //患者信息
            final var patient = patientRepository.getPatient(r.getPatientId());
            // 1.4 获取患者类型
            Integer isChild = (patient == null) ? 1 : patient.FindAgeType();
            // 1.3 获取检测结果和指标
            List<List<Object>> results = bldReportRepository.getResultWithKpis(data, r.getDataAnalysisResult(), 1, isChild);
            // 返回报告
            ReportDtlVO reportDtlVO = bldReportRepository.toReportDtlVO(r, patient, data, wxService.generateNotifyUrl(id, 1));
            ;
            // 结果转换
            resultToVO(r.getReportType(), results, reportDtlVO, r.getReportCode(), r.getStripTypeCode());
            return reportDtlVO;
        }).orElse(null);
    }

    @Override
    public ReportDtlVO getUnionOne(Long id) {
        // 1.1 获取报告
        var reportDO = testReportPoctUniRepository.getById(id);
        return Optional.ofNullable(reportDO).map(r -> {
            //患者信息
            final var patient = patientRepository.getPatient(r.getPatientId());
            // 1.4 获取患者信息
            Integer isChild = (patient == null) ? 1 : patient.FindAgeType();
            // 1.2 获取检测数据A
            final var dataA = this.getDetail(r.getTestPoctDataIdA());
            final var results = bldReportRepository.getResultWithKpis(dataA, r.getAnalysisResultA(), 1, isChild);
            // 1.3 获取检测数据B
            final var dataB = this.getDetail(r.getTestPoctDataIdB());
            results.addAll(bldReportRepository.getResultWithKpis(dataB, r.getAnalysisResultB(), 1, isChild));
            // 1.4 组装检测名称
            final var stripName = dataA.getStripMapping().getStripMapName() + "+" + dataB.getStripMapping().getStripMapName();
            // 1.6 获取病症关联
            final var diseaseCategory = diseaseConfigService.getWithGroupBy(reportDO.getReportCode(), dataA.getValue1(), dataB.getValue1(), 5);
            // 1.7 获取联合项目解读
            TestConfUniProjectDescDTO testConfUniProjectDescDTO = new TestConfUniProjectDescDTO();
            testConfUniProjectDescDTO.setReportCode(r.getReportCode());
            final var projectDesc = testConfUniProjectDescRepository.selectOne(testConfUniProjectDescDTO);
            String sampleType = CommonUtil.processSample(dataA.getStripType().getSampleType(), dataB.getStripType().getSampleType());
            // 返回报告
            var reportDtlVO = bldReportRepository.toReportDtlVO(r, stripName, patient, wxService.generateNotifyUrl(id, 3), projectDesc, sampleType);
            reportDtlVO.setDiseaseCategory(diseaseCategory);
            // 结果转换
            resultToVO(r.getUniAnalyseType(), results, reportDtlVO, r.getReportCode(), r.getStripTypeCodeA());
            return reportDtlVO;
        }).orElse(null);
    }

    @Override
    public void updateOrderStatus(String orderNo) {
        testReportPoctRepository.updateSigReportStatus(orderNo);
        testReportPoctUniRepository.updateUniReportStatus(orderNo);
    }

    private void resultToVO(Integer type, List<List<Object>> results, ReportDtlVO reportDtlVO, String reportCode, Integer stripCode) {
        if (type == 2) {
            List<DxResultVO> dxResult = results.parallelStream().collect(ArrayList::new, (list, result) -> list.add(new DxResultVO(result)), ArrayList::addAll);
            reportDtlVO.setResults(dxResult);
        } else {
            reportDtlVO.setHeaders(dataConfigService.getReportHeaders(reportCode, stripCode));
            reportDtlVO.setRows(results);
        }
    }

    /**
     * 金标仪检测数据
     *
     * @param id
     * @return
     */
    public TestDataDTO getDetail(Long id) {
        TestDataDTO testData = new TestDataDTO();
        var data = testDataDetailRepository.getById(id);
        if (data != null) {
            BeanUtil.copyProperties(data, testData);
            Optional.of(testData).ifPresent(i -> {
                TestConfPoctStripTypeDTO testConfPoctStripTypeDTO = new TestConfPoctStripTypeDTO();
                testConfPoctStripTypeDTO.setTypeCode(i.getStripTypeCode());
                // 获取检测项目
                i.setStripType(confPoctStripTypeRepository.selectOne(testConfPoctStripTypeDTO));
                TestConfPoctStripMappingDTO testConfPoctStripMappingDTO = new TestConfPoctStripMappingDTO();
                testConfPoctStripMappingDTO.setStripTypeCode(i.getStripTypeCode());
                testConfPoctStripMappingDTO.setStripBatch(i.getStripBatch());
                // 获取检测批次
                i.setStripMapping(testConfPoctStripMappingRepository.selectOne(testConfPoctStripMappingDTO));
            });
        }
        return testData;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public BldCheckReportVO createByDataId(Long dataId, String orderNo, Boolean isPay) {
        final var data = bldCheckDataRepository.getById(dataId);
        final var project = checkProjectRepository.selectOne(new CheckProjectDTO().setCheckProjectCode(data.getCheckType()));
        final var patient = patientRepository.findById(data.getPatientId());

        final var projectCode = project.getCheckProjectCode() == 121 ? 100 : project.getCheckProjectCode();
        final var categories = checkItemCategoryRepository.findByProjectCode(projectCode);
        final var results = JSONUtil.toList(data.getCheckResult(), CheckItemResultDTO.class);
        final var resultMap = results.parallelStream().collect(Collectors.toMap(CheckItemResultDTO::getType, CheckItemResultDTO::getValue));
        final var indicators = checkItemIndicatorRepository.selectByParam(projectCode, toAgeInt(patient.getDetail().getCategory(AgeSegment.ADULT_YEAR_STANDARD_BLD)));
        final var indicatorMap = indicators.parallelStream().collect(Collectors.toMap(CheckItemDTO::getItemName, Function.identity()));
        final var preciseJudgeMap = preciseJudeRepository.list(new BloodPreciseJudeDTO()).stream()
                .collect(Collectors.groupingBy(BloodPreciseJudeDTO::getCheckProjectItem));
        final var codeMap = categories.stream().collect(HashMap<String, String>::new, (map, c) -> {
            final var code = c.getItems().stream().map(i -> {
                final var value = resultMap.get(i.getCheckTypeEnname());
                final var indicator = indicatorMap.get(i.getCheckTypeEnname());
                // 一些特殊条目需要更详细的code判断
                final var preciseJudgeList = preciseJudgeMap.get(i.getCheckTypeEnname());
                if (CollUtil.isNotEmpty(preciseJudgeList)) {
                    final var judge = preciseJudgeList.stream()
                            .filter(it -> value.compareTo(it.getMin()) > 0 && value.compareTo(it.getMax()) <= 0)
                            .findFirst().map(BloodPreciseJudeDTO::getJudge).orElse(null);
                    if (StrUtil.isNotBlank(judge)) {
                        return judge;
                    }
                }
                if (indicator.getMaxDanger() != null && value.compareTo(indicator.getMaxDanger()) > 0) {
                    return "5";
                } else if (indicator.getMinDanger() != null && value.compareTo(indicator.getMinDanger()) < 0) {
                    return "1";
                } else if (value.compareTo(indicator.getMaxValue()) > 0) {
                    return "4";
                } else if (value.compareTo(indicator.getMinValue()) > 0) {
                    return "3";
                }
                return "2";
            }).collect(Collectors.joining());
            map.put(c.getAbbreviation(), code);
        }, HashMap::putAll);

        final var report = reportMapping.to(data, project, patient, orderNo, codeMap, isPay);
        //判断是否是联机检测
        var orderCheck = testOrderCheckRecordRepository.selectOne(new TestOrderCheckRecordDTO().setOrderNo(report.getOrderNo()).setIsUni(0).setTestType(2));
        if (orderCheck != null) {
            report.setReportType(2);
            //金标仪待检测
            if (orderCheck.getStatus() == 0) {
                report.setDeleteTime(LocalDateTime.now());
            } else {
                //金标仪已检测
                testReportPoctUniRepository.updateByOrderNo(report.getOrderNo());
            }
        }
        var checkReport = this.insert(report);
        testOrderItemRepository.updateByOrderNO(report.getBloodCheckReportId(), orderNo, 100L);
        testRecordDetailRepository.updateByOrderNo(checkReport, data);
        return reportMapping.to(report, data.getCreateTime());
    }

    /**
     * @param category
     * @return 1:儿童 2：成人男 3：成人女
     */
    public int toAgeInt(PatientCategory category) {
        if (category.equals(PatientCategory.ADULT_MALE)) {
            return 2;
        } else if (category.equals(PatientCategory.ADULT_FEMALE)) {
            return 3;
        } else if (category.equals(PatientCategory.CHILD_MALE) || category.equals(PatientCategory.CHILD_FEMALE)) {
            return 1;
        }
        return 2;
    }

    /**
     * 根据报告生成推送二维码
     *
     * @param reportId
     * @param type
     * @return
     */
    public String generateNotifyUrl(Long reportId, Integer type) {
        return getWxUrlWithExpire(30 * 24 * 3600, reportId, String.valueOf(type), enyType);
    }

    /**
     * 获取微信推送二维码链接
     *
     * @param expire_seconds
     * @param reportId
     * @param reportType
     * @param envirType
     * @return
     */
    public String getWxUrlWithExpire(int expire_seconds, Long reportId, String reportType, String envirType) {
        var accessToken = getAccessToken();
        String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + accessToken;
        JSONObject body = new JSONObject();
        body.set("expire_seconds", expire_seconds);
        body.set("action_name", "QR_STR_SCENE");
        JSONObject sid = new JSONObject();
        sid.set("scene_str", reportType + envirType + reportId);
        JSONObject scene = new JSONObject();
        scene.set("scene", sid);
        body.set("action_info", scene);

        JSONObject result = JSONUtil.parseObj(HttpUtil.post(url, body.toString()));
        var qrUrl = result.get("url");
        if (qrUrl == null) {
            // 获取失败，token过期，删除缓存中的token
            if (result.get("errcode") != null) {
                redisTemplate.delete(WEXIN_TOKEN_KEY);
            }
            log.error("推送二维码获取失败：微信返回结果：{}" + result);
            return null;
        }
        return qrUrl.toString();
    }

    /**
     * 获取微信授权
     *
     * @return
     */
    public String getAccessToken() {
        String accessToken = (String) redisTemplate.opsForValue().get(WEXIN_TOKEN_KEY);
        if (accessToken == null) {
            String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
                    + wxProperties.getAppId() + "&secret=" + wxProperties.getSecret();
            JSONObject result = JSONUtil.parseObj(HttpUtil.get(url));
            var tokenObj = result.get("access_token");
            if (ObjectUtil.isNull(tokenObj)) {
                log.error("微信Token获取失败, 微信返回结果:{}", tokenObj);
                return "";
            }
            accessToken = tokenObj.toString();
            redisTemplate.opsForValue().set(WEXIN_TOKEN_KEY, accessToken, 60, TimeUnit.MINUTES);
            refreshOldToken(accessToken);
        }
        return accessToken;
    }

    /**
     * 刷新老系统的token
     *
     * @param token
     */
    private void refreshOldToken(String token) {
        var url = wxProperties.getRefreshUrl() + "?token=" + token + "&md5=" + MD5.create().digestHex(token + "000390c152af4c489d158093b74ec4ac");
        try {
            var result = HttpUtil.get(url, 2000);
            log.info("更新.NET服务Token结果", result);
        } catch (Exception e) {
            log.error("更新.NET服务微信Token异常，原因：{}", e);
        }
    }

    /**
     * 报告组成
     *
     * @param reportRequestDTO
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createReport(ReportRequestDTO reportRequestDTO) {
        String orderNo = reportRequestDTO.getReportReqDTO().getOrderNo();
        EHomeAssert.isTrue(StrUtil.isNotBlank(orderNo), OrderBizCodeEnums.ORDER_NO_IS_NULL);
        TestOrderDTO orderDTO = testOrderService.selectOne(new TestOrderDTO().setTestOrderNo(orderNo));
        EHomeAssert.notNull(orderDTO, OrderBizCodeEnums.NOT_FOUND_TEST_ORDER, orderNo);
        EHomeAssert.isTrue(BusinessOrderTypeEnums.PRE_TRADE.equals(orderDTO.getBusinessOrderType()), OrderBizCodeEnums.ORDER_BUSINESS_FAIL);

        //所有检测项目
        var dataReportConfigList = dataConfigService.list();
        //报告组成
        var dataReportDetermines = determineDatasReport(reportRequestDTO.getTestData(), dataReportConfigList);
        //患者信息
        var patient = termPatientRepository.getById(reportRequestDTO.getReportReqDTO().getPatientId());
        var isChild = patient == null ? 1 : patient.FindAgeType();
        List<TestReportPoctDTO> sigTestReportPoctDOs = this.buildSigleReports(dataReportDetermines, reportRequestDTO.getReportReqDTO(), isChild);
        List<TestReportPoctUniDTO> uniTestReportPoctUniDOs = this.buildUnionReports(dataReportDetermines, reportRequestDTO.getReportReqDTO(), isChild);
//        String orderDesc = this.createOrderDesc(sigTestReportPoctDOs,uniTestReportPoctUniDOs);
        //保存订单信息
        var reportItemDTOList = saveReports(sigTestReportPoctDOs, uniTestReportPoctUniDOs);
        AtomicBoolean atomicBoolean = new AtomicBoolean(false);
        List<String> snoList = new ArrayList<>();
        //回写订单项信息
        reportItemDTOList.forEach(reportItem -> {
            boolean ignoreSaaAndCrp = ignoreSaaAndCrp(reportItem, reportRequestDTO.getReportReqDTO().getOrderNo());
            if (ignoreSaaAndCrp) {
                atomicBoolean.set(true);
                snoList.add(reportRequestDTO.getTestData().get(0).getSno());
                return;
            }
            testOrderItemRepository.updateByOrderNo(reportItem, reportRequestDTO.getReportReqDTO().getOrderNo());

            // 组合产品处理  先注释一下  测试说要复现一下这个场景
            //TestOrderInternalPayService.BUNDLING_REPORT_LIST.forEach(stripeType -> {
            //    if (stripeType.getReportTypeCode().equals(reportItem.getReportCode())) {
            //        reportItem.setStripeCode(stripeType.getStripTypeCode());
            //    }
            //});
            testRecordDetailRepository.updateByOrderNo(reportItem, reportRequestDTO, dataReportConfigList);
        });

        String sno = null;
        if (CollUtil.isNotEmpty(snoList)) {
            sno = snoList.get(0);
        }

        updatePreOrderSaaAndCrpStripe(atomicBoolean.get(), reportRequestDTO.getReportReqDTO().getOrderNo(), sno);
        crpSaaBldUnitRecordUpdate(reportRequestDTO, reportItemDTOList, StripTypeEnums.U_CRP_SAA_BLD_REPORT.getStripTypeCode());

        TestOrderDTO testOrderDTO = testOrderService.getTestOrderByOrderNo(reportRequestDTO.getReportReqDTO().getOrderNo());

        updateOrderExtInfo(reportRequestDTO, testOrderDTO);
    }

    @Override
    public List<ReportDTO> getSummaryJby(String orderNo) {
        List<ReportDTO> reportList = new ArrayList<>();
        // 获取订单项
        List<TestOrderItemDTO> items = testOrderItemRepository.list(new TestOrderItemDTO().setTestOrderNo(orderNo));
        // 获取联合检测code
        List<String> unionCode = items.stream().filter(i -> i.getTestPoctReportType() == 2)
                .map(TestOrderItemDTO::getTestReportTypeCode).collect(Collectors.toList());
        //是否有联机检测
        long count = items.stream().filter(r -> r.getStripTypeCode() != null && r.getStripTypeCode() == 121).count();
        if (count > 0) {
            //联机检测
            reportList.add(new ReportDTO().setReportName("CRP+SAA+血常规联合检测").setReportType(3).setStripTypeCode(121));
        }
        // 如果订单中存在联合检测
        if (CollUtil.isNotEmpty(unionCode)) {
            // 获取组成联合检测的单项检测code

            List<Integer> stripCodes = dataConfigService.getAll(unionCode);
            // 过滤掉组成了联合报告的单项检测，并分组
            Map<Integer, List<Long>> reportIdsMap = items.stream().filter(i ->
                            !CollUtil.contains(stripCodes, i.getStripTypeCode()))
                    .collect(Collectors.groupingBy(TestOrderItemDTO::getTestPoctReportType,
                            Collectors.mapping(TestOrderItemDTO::getTestPoctReportId, Collectors.toList())));
            // 单项检测报告
            if (CollUtil.isNotEmpty(reportIdsMap.get(1))) {
                List<TestReportPoctDTO> sigReport = testReportPoctRepository.getSigReport(reportIdsMap.get(1));
                sigReport.forEach(item -> reportList.add(createReportDTO(item, 1)));
            }
            // 联合检测报告
            if (CollUtil.isNotEmpty(reportIdsMap.get(2))) {
                List<TestReportPoctUniDTO> uniReport = testReportPoctUniRepository.getUniReport(reportIdsMap.get(2));
                uniReport.forEach(item -> reportList.add(createReportDTO(item, 2)));
            }
        }
        // 只有单项检测报告
        else {
            List<Long> reportIds = CollUtil.map(items, TestOrderItemDTO::getTestPoctReportId, true);
            if (CollUtil.isNotEmpty(reportIds)) {
                List<TestReportPoctDTO> sigReport = testReportPoctRepository.getSigReport(reportIds);
                sigReport.forEach(item -> reportList.add(createReportDTO(item, 1)));
            }
        }
        return reportList;
    }

    @Override
    public void reportConfigJby(TestReportConfigDTO testReportConfigDTO) {
        testReportPoctRepository.reportConfigJby(testReportConfigDTO);
        testReportPoctUniRepository.reportConfigJby(testReportConfigDTO);
    }

    @Override
    public void reportConfigBld(TestReportConfigDTO testReportConfigDTO) {
        bldReportRepository.reportConfigBld(testReportConfigDTO);
    }

    @Override
    public List<ReportDTO> simpleReportList(String orderNo) {
        List<ReportDTO> reportDTOS = new ArrayList<>();
        //直接从订单项表里面获取订单项信息
        // 获取订单项
        List<TestOrderItemDTO> orderItem = testOrderItemRepository.list(new TestOrderItemDTO().setTestOrderNo(orderNo));
        //查询血常规项目配置表和金标仪项目配置表
//        List<CheckProjectDTO> xcgProjectConfigList = checkProjectRepository.list();
        List<TestReportDataConfigDTO> jbyProjectConfigList = dataConfigService.list();
        //用来存储联合检测的 检测code,k为订单项id，v为联合检测的code
        Map<Long, Integer> itemStripTypeCodeMap = new HashMap<>();
        //1.首先判断订单项中是否存在  联测
        List<TestOrderItemDTO> unionList = orderItem.stream().filter(f -> f.getStripTypeCode() == null).collect(Collectors.toList());
        List<TestOrderItemDTO> postOrderItem = new ArrayList<>();
        //2.首先判断订单项中是否存在  联测(支付后置代码 stripTypeCode是不为null 和支付完前置相反 恶心死人了 fuck)
        //如果unionList为null表示存在联测 那么就直接往联测里面走。不会存在问题，如果不为null需要再做一层判断
        if (ObjectUtil.isEmpty(unionList)) {
            //取它们的差集，如果存在表示存在“支付后置”生成的订单项
            postOrderItem = orderItem.stream().filter(f -> jbyProjectConfigList.stream().noneMatch(j -> j.getStripTypeCode().equals(f.getStripTypeCode())))
                    .filter(f -> !f.getStripTypeCode().equals(121) && !f.getStripTypeCode().equals(100) && !f.getStripTypeCode().equals(18)).collect(Collectors.toList());
        } else {
            //不为null也需要排查一遍
            List<TestOrderItemDTO> finalUnionList = unionList;
            postOrderItem = orderItem.stream()
                    .filter(f -> finalUnionList.stream().noneMatch(u -> u.getTestOrderItemId().equals(f.getTestOrderItemId())))
                    .filter(f -> jbyProjectConfigList.stream().noneMatch(j -> j.getStripTypeCode().equals(f.getStripTypeCode())))
                    .filter(f -> !f.getStripTypeCode().equals(121) && !f.getStripTypeCode().equals(100) && !f.getStripTypeCode().equals(18))
                    .collect(Collectors.toList());
        }
        //那么我们在代码将相关订单的stripTypeCode赋值为null，让其统一走支付前置的处理逻辑
        if (ObjectUtil.isNotEmpty(postOrderItem)) {
            List<TestOrderItemDTO> finalPostOrderItem = postOrderItem;
            orderItem.stream().filter(f -> finalPostOrderItem.stream().anyMatch(post -> f.getTestOrderItemId().equals(post.getTestOrderItemId()))).forEach(or -> {
                itemStripTypeCodeMap.put(or.getTestOrderItemId(), or.getStripTypeCode());
                or.setStripTypeCode(null);
            });
            //在执行一遍排查联测的逻辑
            unionList = orderItem.stream().filter(f -> f.getStripTypeCode() == null).collect(Collectors.toList());
        }
        //表示没有联测数据，全是单测
        if (ObjectUtil.isEmpty(unionList)) {
            orderItem.forEach(un -> {
                reportDTOS.add(createReportDTO(un, itemStripTypeCodeMap.get(un.getTestOrderItemId())));
            });
        } else {
            //存在联测
            //找到本次检测的项目code 比如：6,17 不包含联测
            List<Integer> nonUniStripCode = orderItem.stream().map(TestOrderItemDTO::getStripTypeCode).filter(Objects::nonNull).collect(Collectors.toList());
            //根据检测项目的code得到联测的配置信息 比如06，17
            List<TestReportDataConfigDTO> uniList = jbyProjectConfigList.stream()
                    .filter(it -> nonUniStripCode.contains(it.getStripTypeCode()) && it.getReportType().equals(2) && !it.getTestPropertyType().equals(4))
                    .collect(Collectors.toList());
            //按照检测编码进行分组0617 : 06 17
            final var configMap = uniList.stream().collect(Collectors.groupingBy(TestReportDataConfigDTO::getReportCode));
            //已处理掉的联测项目code
            List<Integer> uniCodeList = new ArrayList<>();
            for (TestOrderItemDTO testOrderItemDTO : unionList) {
                //根据订单项中的联测code，得到相关的联测配置信息
                List<TestReportDataConfigDTO> testReportDataConfigDTOS = configMap.get(testOrderItemDTO.getTestReportTypeCode());
                //判断本次检测项目中是否包含联测中的code，如果包含那么它为联测，则生成联测订单项目
                List<Integer> currentStripCode = testReportDataConfigDTOS.stream().map(TestReportDataConfigDTO::getStripTypeCode).collect(Collectors.toList());
                boolean contains = nonUniStripCode.containsAll(currentStripCode);
                //包含则为联测
                if (contains) {
                    reportDTOS.add(createReportDTO(testOrderItemDTO, itemStripTypeCodeMap.get(testOrderItemDTO.getTestOrderItemId())));
                    uniCodeList.addAll(currentStripCode);
                }
            }
            //处理完联项，处理单项
            List<Integer> singleStripCode = nonUniStripCode.stream().filter(s -> uniCodeList.stream().distinct().noneMatch(s::equals)).collect(Collectors.toList());
            orderItem.stream().filter(it -> singleStripCode.stream().anyMatch(single -> single.equals(it.getStripTypeCode())))
                    .forEach(it -> {
                        reportDTOS.add(createReportDTO(it, itemStripTypeCodeMap.get(it.getTestOrderItemId())));
                    });
        }
//
//        //根据业务和数据的生成 联测报表的stripTypeCode 一定是null
//        //联测报告的testReportTypeCode 一定是U_下划线开头
//        //查询列表中是否存在联测报告
//        List<TestOrderItemDTO> unionList = items.stream().filter(f -> f.getStripTypeCode() == null).collect(Collectors.toList());
//        //表示没有联测数据，全是单测
//        if (ObjectUtil.isEmpty(unionList)) {
//            items.forEach(it -> {
//                reportDTOS.add(createReportDTO(it));
//            });
//        } else {
//            //存在联测
//            final var reportConfigs = dataConfigService.list();
//            //找到本次检测的项目code 比如：6,17 不包含联测
//            List<Integer> strips = items.stream().map(TestOrderItemDTO::getStripTypeCode).filter(Objects::nonNull).collect(Collectors.toList());
//            //根据检测项目的code得到联测的配置信息 比如06，17
//            List<TestReportDataConfigDTO> uniList = reportConfigs.stream()
//                    .filter(it -> strips.contains(it.getStripTypeCode()) && it.getReportType().equals(2) && !it.getTestPropertyType().equals(4))
//                    .collect(Collectors.toList());
//            //按照检测编码进行分组0617 : 06 17
//            final var configMap = uniList.stream().collect(Collectors.groupingBy(TestReportDataConfigDTO::getReportCode));
//            //已处理掉的联测项目code
//            List<Integer> uniCodeList = new ArrayList<>();
//            for (TestOrderItemDTO testOrderItemDTO : unionList) {
//                //根据订单项中的联测code，等到相关的联测配置信息
//                List<TestReportDataConfigDTO> testReportDataConfigDTOS = configMap.get(testOrderItemDTO.getTestReportTypeCode());
//                //判断本次检测项目中是否包含联测中的code，如果包含那么它为联测，则生成联测订单项目
//                List<Integer> currentStripCode = testReportDataConfigDTOS.stream().map(TestReportDataConfigDTO::getStripTypeCode).collect(Collectors.toList());
//                boolean contains = strips.containsAll(currentStripCode);
//                //包含则为联测
//                if (contains) {
//                    reportDTOS.add(createReportDTO(testOrderItemDTO));
//                    uniCodeList.addAll(currentStripCode);
//                }
//            }
//            //处理完联项，处理单项
//            List<Integer> singleStripCode = strips.stream().filter(s -> uniCodeList.stream().distinct().noneMatch(s::equals)).collect(Collectors.toList());
//            items.stream().filter(it -> singleStripCode.stream().anyMatch(single -> single.equals(it.getStripTypeCode())))
//                    .forEach(it -> {
//                        reportDTOS.add(createReportDTO(it));
//                    });
//        }


        //三联测特殊处理
        reportDTOS.stream().filter(f -> CRP_SAA_BLOOD.equals(f.getStripTypeCode())).forEach(f -> f.setReportType(3));
             /*
                又一处神奇的补丁开始了 。原因是：在用血常规做检查 走正常流程时（支付后置），在生成订单的时候并不会往订单表里面录入订单检测项目名称
                可以看如下两个接口就知道了：
                单测:poct-web-terminal/order/place
                联测：palmpoct-client/api/new/online/create/order、
         */
        Integer code = 100;
        reportDTOS.stream().filter(f -> CharSequenceUtil.isBlank(f.getReportName())).forEach(f -> {
            if (CRP_SAA_BLOOD.equals(f.getStripTypeCode())) {
                f.setReportName("CRP+SAA+血常规联合检测");
            }
            if (code.equals(f.getStripTypeCode())) {
                f.setReportName("血常规三分类");
            }
        });
        return reportDTOS;
    }

    @Override
    public List<ProjectNameReportDTO> projectNameList(List<String> orderNos) {
        List<ProjectNameReportDTO> projectNameList = new ArrayList<>();
        if (ObjectUtil.isEmpty(orderNos)) {
            return projectNameList;
        }
        //直接从订单项表里面获取订单项信息
        // 获取订单项
        Map<String, Object> queryMap = new HashMap<>();
        queryMap.put("testOrderNo", orderNos);
        //得到所有订单的订单项,然后再根据orderNo进行分组
        List<TestOrderItemDTO> list = testOrderItemRepository.list(queryMap);
        Map<String, List<TestOrderItemDTO>> orderMap = list.stream().collect(Collectors.groupingBy(TestOrderItemDTO::getTestOrderNo));

        List<TestOrderDTO> testOrderDTOList = testOrderRepository.list(queryMap);
        Map<String, BusinessOrderTypeEnums> orderTypeMap = testOrderDTOList.stream().filter(orderDTO -> Objects.nonNull(orderDTO.getBusinessOrderType())).collect(Collectors.toMap(TestOrderDTO::getTestOrderNo, TestOrderDTO::getBusinessOrderType));

        //查询血常规项目配置表和金标仪项目配置表
        List<CheckProjectDTO> xcgProjectConfigList = checkProjectRepository.list();
        List<TestReportDataConfigDTO> jbyProjectConfigList = dataConfigService.list();
        //然后循环里面的每个订单里面的每一项
        orderMap.forEach((orderNo, orderItem) -> {
            BusinessOrderTypeEnums orderType = orderTypeMap.get(orderNo);
            // 非支付前置订单
            final boolean isPreTrade = BusinessOrderTypeEnums.PRE_TRADE.equals(orderType);
            //1.首先判断订单项中是否存在  联测
            List<TestOrderItemDTO> unionList = orderItem.stream().filter(f -> f.getStripTypeCode() == null).collect(Collectors.toList());
            List<TestOrderItemDTO> postOrderItem = new ArrayList<>();
            //2.首先判断订单项中是否存在  联测(支付后置代码 stripTypeCode是不为null 和支付完前置相反 恶心死人了 fuck)
            //如果unionList为null表示存在联测 那么就直接往联测里面走。不会存在问题，如果不为null需要再做一层判断
            if (ObjectUtil.isEmpty(unionList)) {
                //取它们的差集，如果存在表示存在“支付后置”生成的订单项
                postOrderItem = orderItem.stream().filter(f -> jbyProjectConfigList.stream().noneMatch(j -> j.getStripTypeCode().equals(f.getStripTypeCode())))
                        .filter(f -> !f.getStripTypeCode().equals(121) && !f.getStripTypeCode().equals(100) && !f.getStripTypeCode().equals(18)).collect(Collectors.toList());
            } else {
                //不为null也需要排查一遍
                List<TestOrderItemDTO> finalUnionList = unionList;
                postOrderItem = orderItem.stream()
                        .filter(f -> finalUnionList.stream().noneMatch(u -> u.getTestOrderItemId().equals(f.getTestOrderItemId())))
                        .filter(f -> jbyProjectConfigList.stream().noneMatch(j -> j.getStripTypeCode().equals(f.getStripTypeCode())))
                        .filter(f -> !f.getStripTypeCode().equals(121) && !f.getStripTypeCode().equals(100) && !f.getStripTypeCode().equals(18))
                        .collect(Collectors.toList());
            }
            //那么我们在代码将相关订单的stripTypeCode赋值为null，让其统一走支付前置的处理逻辑
            if (ObjectUtil.isNotEmpty(postOrderItem)) {
                List<TestOrderItemDTO> finalPostOrderItem = postOrderItem;
                orderItem.stream().filter(f -> finalPostOrderItem.stream().anyMatch(post -> f.getTestOrderItemId().equals(post.getTestOrderItemId()))).forEach(or -> {
                    or.setStripTypeCode(null);
                });
                //在执行一遍排查联测的逻辑
                unionList = orderItem.stream().filter(f -> f.getStripTypeCode() == null).collect(Collectors.toList());

            }
            ProjectNameReportDTO projectNameDto = new ProjectNameReportDTO();
            projectNameDto.setOrderNo(orderNo);
            List<String> projectName = new ArrayList<>();
            //表示没有联测数据，全是单测
            if (ObjectUtil.isEmpty(unionList)) {
                orderItem.forEach(un -> {
                    if (un.getStripTypeCode() == 121 || un.getStripTypeCode() == 100) {
                        projectName.add(xcgProjectConfigList.stream().filter(f -> f.getCheckProjectCode().equals(un.getStripTypeCode())).findFirst().orElse(new CheckProjectDTO()).getCheckProjectName());
                    } else if (un.getStripTypeCode() == 18 && !isPreTrade) {
                        //18 特殊的4、5联测。别问为什么 我也不知道
                        projectName.add("CRP+SAA双联");
                    } else {
                        TestReportDataConfigDTO testReportDataConfigDTO = jbyProjectConfigList.stream().filter(f -> f.getReportCode().equals(un.getTestReportTypeCode()) && f.getStripTypeCode().equals(un.getStripTypeCode())).findFirst().orElse(new TestReportDataConfigDTO());
                        projectName.add(testReportDataConfigDTO.getStripName());
                    }
                });
            } else {
                //存在联测
                //找到本次检测的项目code 比如：6,17 不包含联测
                List<Integer> nonUniStripCode = orderItem.stream().map(TestOrderItemDTO::getStripTypeCode).filter(Objects::nonNull).collect(Collectors.toList());
                //根据检测项目的code得到联测的配置信息 比如06，17
                List<TestReportDataConfigDTO> uniList = jbyProjectConfigList.stream()
                        .filter(it -> nonUniStripCode.contains(it.getStripTypeCode()) && it.getReportType().equals(2) && !it.getTestPropertyType().equals(4))
                        .collect(Collectors.toList());
                //按照检测编码进行分组0617 : 06 17
                final var configMap = uniList.stream().collect(Collectors.groupingBy(TestReportDataConfigDTO::getReportCode));
                //已处理掉的联测项目code
                List<Integer> uniCodeList = new ArrayList<>();
                for (TestOrderItemDTO testOrderItemDTO : unionList) {
                    //根据订单项中的联测code，得到相关的联测配置信息
                    List<TestReportDataConfigDTO> testReportDataConfigDTOS = configMap.get(testOrderItemDTO.getTestReportTypeCode());
                    //判断本次检测项目中是否包含联测中的code，如果包含那么它为联测，则生成联测订单项目
                    List<Integer> currentStripCode = testReportDataConfigDTOS.stream().map(TestReportDataConfigDTO::getStripTypeCode).collect(Collectors.toList());
                    boolean contains = nonUniStripCode.containsAll(currentStripCode);
                    //包含则为联测
                    if (contains) {
                        projectName.add(jbyProjectConfigList.stream().filter(f -> f.getReportCode().equals(testOrderItemDTO.getTestReportTypeCode())).findFirst().orElse(new TestReportDataConfigDTO()).getStripName());
                        uniCodeList.addAll(currentStripCode);
                    }
                }
                //处理完联项，处理单项
                List<Integer> singleStripCode = nonUniStripCode.stream().filter(s -> uniCodeList.stream().distinct().noneMatch(s::equals)).collect(Collectors.toList());
                orderItem.stream().filter(it -> singleStripCode.stream().anyMatch(single -> single.equals(it.getStripTypeCode())))
                        .forEach(it -> {
                            if (it.getStripTypeCode() == 121 || it.getStripTypeCode() == 100) {
                                projectName.add(xcgProjectConfigList.stream().filter(f -> f.getCheckProjectCode().equals(it.getStripTypeCode())).findFirst().orElse(new CheckProjectDTO()).getCheckProjectName());
                            } else if (it.getStripTypeCode() == 18 && !isPreTrade) {
                                //18 特殊的4、5联测。别问为什么 我也不知道
                                projectName.add("CRP+SAA双联");
                            } else {
                                TestReportDataConfigDTO testReportDataConfigDTO = jbyProjectConfigList.stream().filter(f -> f.getReportCode().equals(it.getTestReportTypeCode()) && f.getStripTypeCode().equals(it.getStripTypeCode())).findFirst().orElse(new TestReportDataConfigDTO());
                                projectName.add(testReportDataConfigDTO.getStripName());
                            }
                        });
            }
            projectNameDto.setReportName(projectName.stream().filter(StrUtil::isNotBlank).collect(Collectors.toList()));
            projectNameList.add(projectNameDto);
        });
        return projectNameList;
    }

    /**
     * 1-金标仪单测 2-血液分析仪单测 3-金标仪联测 4-金标仪/血液分析仪联测 5-生化仪单测
     *
     * @param item
     * @return
     */
    private ReportDTO createReportDTO(TestOrderItemDTO item, Integer stripCode) {
        if (ObjectUtil.isNull(item.getStripTypeCode())) {
            item.setStripTypeCode(stripCode);
        }
        ReportDTO reportDTO = new ReportDTO().setReportId(item.getTestPoctReportId()).setReportType(item.getTestPoctReportType()).setReportName(item.getOrderItemName()).setStripTypeCode(item.getStripTypeCode());
        // 报告类型 (1：单测 2：联测,3-血常规联测)
        Integer testPoctReportType = item.getTestPoctReportType();
        Integer stripTypeCode = item.getStripTypeCode();
        switch (testPoctReportType) {
            case 1: {
                return reportDTO.setReportAnalysisType((stripTypeCode == 121 || stripTypeCode == 100) ? 2 : 1);
            }
            case 2: {
                return reportDTO.setReportAnalysisType(3);
            }
            case 3: {
                return reportDTO.setReportAnalysisType(4);
            }
            default: {
                return reportDTO;
            }
        }
    }

    private ReportDTO createReportDTO(TestReportPoctDTO item, Integer type) {
        return new ReportDTO().setReportId(item.getTestReportPoctId()).setReportType(type).setReportName(item.getName()).setStripTypeCode(item.getStripTypeCode());
    }

    private ReportDTO createReportDTO(TestReportPoctUniDTO item, Integer type) {
        return new ReportDTO().setReportId(item.getTestReportUniPoctId()).setReportType(type).setReportName(item.getName());
    }

    /**
     * 使用事务提交 保存报告数据
     *
     * @param sigTestReportPoctDOs    单项报告实体
     * @param uniTestReportPoctUniDOs 联合报告实体
     * @return true 成功
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
    public List<ReportItemDTO> saveReports(List<TestReportPoctDTO> sigTestReportPoctDOs, List<TestReportPoctUniDTO> uniTestReportPoctUniDOs) {
        List<ReportItemDTO> list = new ArrayList<>();
        if (!sigTestReportPoctDOs.isEmpty()) {
            List<TestReportPoctDTO> testReportPoctDTOS = testReportPoctRepository.batchInsert(sigTestReportPoctDOs);
            if (testReportPoctDTOS.isEmpty()) {
                throw new BizException(BizCode.CHECK_SINGLE_REPORT_DATA_SAVE_FAILD);
            }
            Map<Integer, Integer> stripeCodeMap = sigTestReportPoctDOs.stream().collect(Collectors.toMap(TestReportPoctDTO::getStripTypeCode, TestReportPoctDTO::getRealStripTypeCode));
            //回写订单项
            testReportPoctDTOS.forEach(r -> {
                ReportItemDTO reportItemDTO = new ReportItemDTO().setReportId(r.getTestReportPoctId()).setReportCode(r.getReportCode()).setStripeCode(r.getStripTypeCode());
                reportItemDTO.setRealStripTypeCode(stripeCodeMap.get(r.getStripTypeCode()));
                list.add(reportItemDTO);
            });
        }
        if (!uniTestReportPoctUniDOs.isEmpty()) {
            List<TestReportPoctUniDTO> testReportPoctUniDTOS = testReportPoctUniRepository.batchInsert(uniTestReportPoctUniDOs);
            final Map<String, Integer> unitReportStripeCodeMap = uniTestReportPoctUniDOs.stream()
                    .filter(unitReport -> Objects.nonNull(unitReport.getUnitStripeCode()))
                    .collect(Collectors.toMap(TestReportPoctUniDTO::getReportCode, TestReportPoctUniDTO::getUnitStripeCode));
            //血常规联测完成状态
            var orderCheck = testOrderCheckRecordRepository.selectOne(new TestOrderCheckRecordDTO().setTestType(1).setIsUni(0).setOrderNo(uniTestReportPoctUniDOs.get(0).getPayOrderNo()));
            if (orderCheck != null) {
                //血常规待检测
                if (orderCheck.getStatus() == 0) {
                    var uniTestReports = testReportPoctUniDTOS.stream().filter(r -> "U_CRP_SAA_BLD_REPORT".equals(r.getReportCode())).findFirst().orElse(new TestReportPoctUniDTO());
                    testReportPoctUniRepository.delete(uniTestReports.getTestReportUniPoctId());
                } else {
                    //血常规已检测
                    bldReportRepository.updateByOrderNo(uniTestReportPoctUniDOs.get(0).getPayOrderNo());
                }
            }
            if (testReportPoctUniDTOS.isEmpty()) {
                throw new BizException(BizCode.CHECK_MULTI_REPORT_DATA_SAVE_FAILD);
            }
            //回写订单项
            testReportPoctUniDTOS.forEach(r -> {
                list.add(new ReportItemDTO().setReportId(r.getTestReportUniPoctId()).setReportCode(r.getReportCode()).setStripeCode(unitReportStripeCodeMap.get(r.getReportCode())));
            });
        }

        return list;
    }

    /**
     * 生成订单描述
     *
     * @param testReportPoctDOS    单项检测报告实体
     * @param testReportPoctUniDOS 联合检测报告实体
     * @return 订单的描述
     */
    public String createOrderDesc(List<TestReportPoctDTO> testReportPoctDOS, List<TestReportPoctUniDTO> testReportPoctUniDOS) {
        String orderDesc = "此检测订单由单项报告 【";
        orderDesc += testReportPoctDOS.stream().map(TestReportPoctDTO::getName).collect(Collectors.joining("+")) + "】 ";
        if (CollUtil.isNotEmpty(testReportPoctUniDOS)) {
            orderDesc += "和联合报告 【" + testReportPoctUniDOS.stream().map(TestReportPoctUniDTO::getName).collect(Collectors.joining("+")).replace("&", "") + " 】";
        }
        orderDesc += "组成";
        return orderDesc;
    }

    private List<TestReportPoctUniDTO> buildUnionReports(List<DataReportDetermine> dataReportDetermines, ReportReqDTO reportReqDTO, int isChild) {
        List<String> reportCodes = dataReportDetermines.stream().map(DataReportDetermine::getReportCode).distinct().collect(Collectors.toList());
        final Map<String, Integer> combineCodeStripeCodeMap = getCombineCodeStripeCode(reportReqDTO);

        //是否有联机检测
        var orderItem = testOrderItemRepository.selectOne(new TestOrderItemDTO().setTestOrderNo(reportReqDTO.getOrderNo()).setStripTypeCode(121));
        List<TestReportPoctUniDTO> uniTestReportPoctUniDOs = reportCodes.stream().collect(ArrayList::new, (list, item) -> {
            List<DataReportDetermine> dataTemps = dataReportDetermines.stream().filter(t -> t.getReportCode().equals(item)).collect(Collectors.toList());
            if (CollUtil.isEmpty(dataTemps)) {
                return;
            }
            String reportName = dataTemps.stream().map(DataReportDetermine::getReportName).distinct().findFirst().orElse(ReportConst.TEST_ORDER_NAME);
            String reportCode = item;
            dataTemps = dataTemps.stream().sorted(Comparator.comparing(DataReportDetermine::getUinonOrder)).collect(Collectors.toList());
            if (orderItem != null && ("U_CRP_SAA_REPORT".equals(item) || "S_CRP_SAA_REPORT".equals(item))) {
                reportName = "CRP&SAA+血常规联合检测";
                reportCode = "U_CRP_SAA_BLD_REPORT";
                dataTemps.get(0).setTestPropertyType(5);
            }
            TestConfPoctUniAnalysisDTO uniTestPoctAnalysisDOs = testConfPoctUniAnalysisRepository.getAnalysisByData(dataTemps.get(0).getTestData(), dataTemps.get(1).getTestData(), isChild);
            if (null == uniTestPoctAnalysisDOs) {
                throw new BizException(BizCode.REPORT_ANALYSIS_CONFIG_FOR_ANALYSIS_MISSING);
            }

            ClinicalAnaluseResultDto clinicalresult = null;
            //检测这个类型报告有没有 临床智能分析的要求
            if (1 == dataTemps.get(0).getHaveClinicalAnalyse()) {
                clinicalresult = new ClinicalAnaluseResultDto();
                //拿到这个报告的临床编码
                TestConfClinicalReportStandardDTO standard = null;
                String tempCode = item;
                if ("S_CRP_SAA_REPORT".equals(item)) {
                    //特殊处理CRP SAA 双联试纸 双联试纸和CRP SAA 联测一样的，本质是同一个东西放在了一张纸
                    tempCode = "U_CRP_SAA_REPORT";
                }
                if (2 == dataTemps.get(0).getTestPropertyType()) {
                    //如果是定性的 拿到这值的阴阳性，有可能是两个阴阳性
                    standard = testConfClinicalReportStandardRepository.getByResult(tempCode,
                            uniTestPoctAnalysisDOs.getStripTypeAResult() + "/" + uniTestPoctAnalysisDOs.getStripTypeBResult(), true, isChild);
                } else {

                    standard = testConfClinicalReportStandardRepository.getByRange(tempCode, dataTemps.get(0).getStripTypeCode(), dataTemps.get(0).getTestData().getValue1(),
                            dataTemps.get(1).getStripTypeCode(), dataTemps.get(1).getTestData().getValue1(), true
                            , isChild);
                }
                if (null != standard) {
                    TestConfClinicalReportAnalyseDTO analyse = testConfClinicalReportAnalyseRepository.getByCode(standard.getClinicalAnalyseCode(), isChild);
                    if (null != analyse) {
                        clinicalresult.setClinicalAnalyseCode(analyse.getClinicalAnalyseCode());
                        clinicalresult.setClinicalAnalyseLevel(analyse.getNoticeLevel());
                    }
                }
            }

            final TestReportPoctUniDTO reportPoctUniDTO = dataReportDetermineMapping.to(reportReqDTO.getOrderNo(), reportCode, reportName, dataTemps.get(0), dataTemps.get(1), dataTemps.get(0).getTestData(), dataTemps.get(1).getTestData(), uniTestPoctAnalysisDOs, reportReqDTO, clinicalresult);
            String finalReportCode = reportCode;
            Optional<DataReportDetermine> determineOptional = dataTemps.stream().filter(dataReportDetermine -> dataReportDetermine.getReportCode().equals(finalReportCode)).findFirst();
            determineOptional.ifPresent(dataReportDetermine -> {
                Integer stripeCode = combineCodeStripeCodeMap.get(dataReportDetermine.getReportCode());
                reportPoctUniDTO.setUnitStripeCode(stripeCode);
            });

            // 三联测特殊处理
            if (StripTypeEnums.U_CRP_SAA_BLD_REPORT.name().equalsIgnoreCase(reportCode)) {
                reportPoctUniDTO.setUnitStripeCode(StripTypeEnums.U_CRP_SAA_BLD_REPORT.getStripTypeCode());
            }
            list.add(reportPoctUniDTO);
        }, ArrayList::addAll);
        dataReportDetermines.clear();
        return uniTestReportPoctUniDOs;
    }

    /**
     * 获取组合代码的检测代码 联测报告根据
     *
     * @param reportReqDTO
     * @return
     */
    private Map<String, Integer> getCombineCodeStripeCode(ReportReqDTO reportReqDTO) {
        EHomeAssert.notNull(reportReqDTO.getOrderNo(), OrderBizCodeEnums.ORDER_NO_IS_NULL);
        TestOrderDTO testOrderDTO = testOrderService.selectOne(new TestOrderDTO().setTestOrderNo(reportReqDTO.getOrderNo()));
        EHomeAssert.notNull(testOrderDTO, OrderBizCodeEnums.NOT_FOUND_TEST_ORDER, reportReqDTO.getOrderNo());
        ClinicDTO clinicDTO = clinicClient.getById(testOrderDTO.getClinicId());
        EHomeAssert.notNull(clinicDTO, ClinicBizCodeEnums.NO_FOUND_CLINIC);
        List<PromotionBundlePriceProfitConfDTO> priceProfitConfDTOList = promotionBundlePriceProfitConfRepository.getValidBundlerPriceInfos(clinicDTO.getTestStripPriceProfitSchemeId());
        if (CollUtil.isEmpty(priceProfitConfDTOList)) {
            return MapUtil.newHashMap();
        }
        return priceProfitConfDTOList.stream()
                .filter(confDTO -> StrUtil.isNotBlank(confDTO.getProductCombineCode()))
                .collect(Collectors.toMap(PromotionBundlePriceProfitConfDTO::getReportCode, PromotionBundlePriceProfitConfDTO::getStripTypeCode, (o1, o2) -> o1));
    }

    /**
     * 生成单项的报告数据
     * 伊鸿业务规则：如果有联测的两个单项数据，也会生成单项的的数据。
     *
     * @param dataReportDetermineList 检测数据类型 会生成那些报告判读
     * @param reportReqDTO            检测订单请求信息
     * @return List<TestReportPoctDO>  生成单项的报告数据实体
     */
    public List<TestReportPoctDTO> buildSigleReports(List<DataReportDetermine> dataReportDetermineList, ReportReqDTO reportReqDTO, Integer isChild) {
        List<DataReportDetermine> sigDataReportDetermins = dataReportDetermineList.stream().filter(t -> t.getReportType().equals(sigReportType)).collect(Collectors.toList());
        sigDataReportDetermins = sigDataReportDetermins.stream().sorted(Comparator.comparing(DataReportDetermine::getStripTypeCode)).collect(Collectors.toList());

        List<TestReportPoctDTO> sigTestReportPoctDOs = sigDataReportDetermins.stream().collect(ArrayList::new, (list, item) -> {
            TestConfPoctAnalysisDTO testConfPoctAnalysisDTO = testConfPoctAnalysisRepository.getAnalysisByData(item.getTestData(), isChild);
            if (null == testConfPoctAnalysisDTO) {
                throw new BizException(20040202, String.format("没有找到(%s)对应的(%s)批次解读信息！", item.getReportName(), item.getTestData().getStripBatch()));
            }

            ClinicalAnaluseResultDto clinicalresult = null;

            //检测这个类型报告有没有 临床智能分析的要求
            if (1 == item.getHaveClinicalAnalyse()) {
                clinicalresult = new ClinicalAnaluseResultDto();
                //拿到这个报告的临床编码
                TestConfClinicalReportStandardDTO standard = null;
                if (2 == item.getTestPropertyType()) {
                    //如果是定性的 拿到这值的阴阳性，有可能是两个阴阳性
                    standard = testConfClinicalReportStandardRepository.getByResult(item.getReportCode(),
                            testConfPoctAnalysisDTO.getResult(), item.getTestData().isMultiValue(), isChild);
                } else {
                    standard = testConfClinicalReportStandardRepository.getByRange(item.getReportCode(), item.getStripTypeCode(),
                            item.getTestData().getValue1(), item.getStripTypeCode(),
                            item.getTestData().getValue2(), item.getTestData().isMultiValue(), isChild);
                }
                if (null != standard) {
                    TestConfClinicalReportAnalyseDTO analyse = testConfClinicalReportAnalyseRepository.getByCode(standard.getClinicalAnalyseCode(), isChild);
                    if (null != analyse) {
                        clinicalresult.setClinicalAnalyseCode(analyse.getClinicalAnalyseCode());
                        clinicalresult.setClinicalAnalyseLevel(analyse.getNoticeLevel());
                    }
                }
            }

            list.add(dataReportDetermineMapping.to(item, reportReqDTO, testConfPoctAnalysisDTO, reportReqDTO.getOrderNo(), clinicalresult));
        }, ArrayList::addAll);
        dataReportDetermineList.removeAll(sigDataReportDetermins);
        sigDataReportDetermins.clear();
        return sigTestReportPoctDOs;
    }

    /**
     * 传入检测数据和配置，返回 报告由哪些组成
     *
     * @param testData             检测数据集合
     * @param dataReportConfigList 报告配置集合
     */
    public List<DataReportDetermine> determineDatasReport(List<TestDataDetailVO> testData, List<TestReportDataConfigDTO> dataReportConfigList) {
        if (CollUtil.isEmpty(dataReportConfigList)) {
            throw new BizException(BizCode.REPORT_CONFIG_INFO_MISSING);
        }
        Map<String, List<TestDataDetailVO>> strips = testData.stream().collect(Collectors.groupingBy(t -> t.getStripTypeCode() + "_" + t.getRealStripTypeCode()));
        if (strips.size() != testData.size()) {
            throw new BizException(BizCode.CHECK_DATA_CONTAIN_REPETITION_STRIP);
        }
        List<DataReportDetermine> dataReportDetermineList = testData.stream().collect(ArrayList::new, (list, item) -> {
            List<TestReportDataConfigDTO> temp = dataReportConfigList.stream().filter(t -> item.getStripTypeCode().equals(t.getStripTypeCode()) && t.getRealStripTypeCode().equals(item.getRealStripTypeCode())).collect(Collectors.toList());
            if (CollUtil.isEmpty(temp)) {
                return;
            }
            List<DataReportDetermine> ddTemp = dataReportDetermineMapping.to(temp);
            ddTemp.forEach(i -> i.setTestData(item));
            list.addAll(ddTemp);
        }, ArrayList::addAll);

        List<DataReportDetermine> dataReportDetermineUnioList = dataReportDetermineList.stream().filter(t -> t.getReportType().equals(unionReportType)).collect(Collectors.toList());
        List<String> reportTypes = dataReportDetermineUnioList.stream().map(DataReportDetermine::getReportCode).distinct().collect(Collectors.toList());
        reportTypes.forEach(
                item -> {
                    if (dataReportDetermineUnioList.stream().filter(t -> t.getReportCode().equals(item)).count() < unionSize) {
                        dataReportDetermineList.remove(dataReportDetermineUnioList.stream().filter((t -> t.getReportCode().equals(item))).findFirst().orElse(null));
                    }
                }
        );
        dataReportDetermineUnioList.clear();
        reportTypes.clear();
        return dataReportDetermineList;
    }

    /**
     * realStripTypeCode = 18； SAA + CRP 特殊处理；先下单 后付款无法确定用的是单试纸还是双试纸
     */
    private boolean ignoreSaaAndCrp(ReportItemDTO reportItemDTO, String orderNo) {
        String duplexCode = reportItemDTO.getStripeCode() + "_" + reportItemDTO.getRealStripTypeCode();
        if ("4_18".equals(duplexCode) || "5_18".equals(duplexCode)) {
            reportItemDTO.setIsDelete(Boolean.TRUE);
            testOrderItemRepository.delOrderItem(reportItemDTO, orderNo);
            return true;
        }
        return false;
    }

    /**
     * 支付前置订单,无法区分是否双联试纸，存在双联执行更新操作 先苟一下
     * SAA CRP双联试纸特殊处理
     *
     * @param sno
     * @param duplexTestPaper
     */
    private void updatePreOrderSaaAndCrpStripe(boolean duplexTestPaper, String orderNo, String sno) {
        if (!duplexTestPaper) {
            testRecordDetailRepository.delSaaAndCrpRecord(orderNo, StripTypeEnums.SAA_CRP.getStripTypeCode());
            testOrderItemRepository.updateStripTypeCodeByTestOrderNoAndStripTypeCode(null, orderNo, StripTypeEnums.SAA_CRP.getStripTypeCode());
            return;
        }
        // 查询 CRP SAA检测记录
        List<TestRecordDetailDTO> recordDetailDTOList = testRecordDetailRepository.findByOrderNoAndStripTypeCodeIn(orderNo, Lists.newArrayList(StripTypeEnums.CRP.getStripTypeCode(), StripTypeEnums.SAA.getStripTypeCode()));
        deleteRecordAndUpdateOrderItem(orderNo);
        TestReportPoctUniDTO reportPoctUniDTO = testReportPoctUniRepository.getUnitReportByOrderNo(orderNo, StripTypeEnums.CRP.getStripTypeCode(), StripTypeEnums.SAA.getStripTypeCode());
        if (Objects.isNull(reportPoctUniDTO)) {
            return;
        }

        BigDecimal testMoney = recordDetailDTOList.stream().map(TestRecordDetailDTO::getTestMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
        saveRecordDetailsInfo(orderNo, sno, reportPoctUniDTO, testMoney);
    }

    /**
     * 删除检测记录（4、5）
     * 修改子商品（18）检测金额
     *
     * @param orderNo
     */
    private void deleteRecordAndUpdateOrderItem(String orderNo) {
        List<TestOrderItemDTO> orderItemDTOList = testOrderItemRepository.findList(orderNo, Lists.newArrayList(StripTypeEnums.SAA.getStripTypeCode(), StripTypeEnums.CRP.getStripTypeCode()));
        BigDecimal feeTotal = orderItemDTOList.stream().map(TestOrderItemDTO::getFee).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
        testRecordDetailRepository.delSaaAndCrpRecord(orderNo, StripTypeEnums.CRP.getStripTypeCode());
        testRecordDetailRepository.delSaaAndCrpRecord(orderNo, StripTypeEnums.SAA.getStripTypeCode());

        TestOrderItemDTO orderItemDTO = new TestOrderItemDTO();
        orderItemDTO.setTestOrderNo(orderNo);
        orderItemDTO.setFee(feeTotal);
        orderItemDTO.setTestReportTypeCode("S_CRP_SAA_REPORT");
        orderItemDTO.setOrderItemName("CRP+SAA双联试纸检测");
        testOrderItemRepository.updateByTestOrderNoAndStripTypeCode(orderItemDTO, orderNo, StripTypeEnums.SAA_CRP.getStripTypeCode());
    }

    /**
     * 保存检测记录
     *
     * @param orderNo
     * @param sno
     * @param reportPoctUniDTO
     * @param testMoney
     */
    private void saveRecordDetailsInfo(String orderNo, String sno, TestReportPoctUniDTO reportPoctUniDTO, BigDecimal testMoney) {
        // test time 、report_id、sno、人份
        // 天坑价格方案改不动 todo list
        TestRecordDetailDTO recordDetailDTO = new TestRecordDetailDTO();
        recordDetailDTO.setOrderNo(orderNo);
        recordDetailDTO.setTestTime(LocalDateTime.now());
        recordDetailDTO.setTestReportId(reportPoctUniDTO.getTestReportUniPoctId());
        recordDetailDTO.setSno(sno);
        recordDetailDTO.setTestMoney(testMoney);
        recordDetailDTO.setTestPeoplePortion(BigDecimal.ONE);
        recordDetailDTO.setStripTypeCode(StripTypeEnums.SAA_CRP.getStripTypeCode());
        testRecordDetailRepository.updateByOrderNoAndStripTypeCode(recordDetailDTO);
    }


    /**
     * 三联测检测记录更新 sno2、testPeoplePortion2
     *
     * @param reportRequestDTO
     * @param reportItemList
     * @param stripTypeCode
     */
    private void crpSaaBldUnitRecordUpdate(ReportRequestDTO reportRequestDTO, List<ReportItemDTO> reportItemList, Integer stripTypeCode) {
        Optional<ReportItemDTO> optional = reportItemList.stream().filter(reportItemDTO -> stripTypeCode.equals(reportItemDTO.getStripeCode())).findFirst();
        if (optional.isEmpty()) {
            return;
        }
        List<String> clinicDeviceSnoList = getClinicDeviceSnoList(reportRequestDTO.getReportReqDTO().getClinicId());
        clinicDeviceSnoList = clinicDeviceSnoList.stream().filter(sno -> !sno.equals(reportRequestDTO.getTestData().get(0).getSno())).collect(Collectors.toList());
        if (CollUtil.isEmpty(clinicDeviceSnoList)) {
            return;
        }
        String sno = clinicDeviceSnoList.get(0);
        TestRecordDetailDTO recordDetailDTO = new TestRecordDetailDTO();
        recordDetailDTO.setOrderNo(reportRequestDTO.getReportReqDTO().getOrderNo());
        recordDetailDTO.setStripTypeCode(stripTypeCode);
        recordDetailDTO.setSno2(sno);
        recordDetailDTO.setTestPeoplePortion2(BigDecimal.ONE);
        testRecordDetailRepository.updateUnitReportSno2(recordDetailDTO);
    }

    /**
     * 获取诊所设备sno
     *
     * @param clinicId
     * @return
     */
    private List<String> getClinicDeviceSnoList(Long clinicId) {
        List<TestDeviceVO> testDeviceVOList = deviceClient.list(new TestDeviceQry().setTermClinicId(clinicId));
        return testDeviceVOList.stream()
                .map(TestDeviceVO::getSno)
                .collect(Collectors.toList());
    }

    /**
     * 保存recordDtls ext信息
     *
     * @param order
     * @param recordDtls
     */
    private void saveRecordDetailsExtInfo(TestOrderDTO order, List<TestRecordDetailDTO> recordDtls) {
        ClinicDTO clinicDTO = clinicClient.getById(order.getClinicId());
        final TermAgentOrgDTO termAgentOrgDTO = termAgentOrgClient.getByAgentId(clinicDTO.getTermAgentId());
        List<TestRecordDetailExtDTO> detailExtDTOList = recordDtls.stream().map(recordDetail -> {
            TestRecordDetailExtDTO recordDetailExtDTO = new TestRecordDetailExtDTO();
            recordDetailExtDTO.setRecordId(recordDetail.getRecordId());
            recordDetailExtDTO.setAgentId(termAgentOrgDTO.getId());
            recordDetailExtDTO.setAgentName(termAgentOrgDTO.getName());
            recordDetailExtDTO.setCityAgentId(termAgentOrgDTO.getCityAgentId());
            recordDetailExtDTO.setCityAgentName(termAgentOrgDTO.getCityAgentName());
            recordDetailExtDTO.setProvinceAgentId(termAgentOrgDTO.getProvinceAgentId());
            recordDetailExtDTO.setProvinceAgentName(termAgentOrgDTO.getProvinceAgentName());
            recordDetailExtDTO.setDivisionAgentId(termAgentOrgDTO.getDivisionAgentId());
            recordDetailExtDTO.setDivisionAgentName(termAgentOrgDTO.getDivisionAgentName());
            return recordDetailExtDTO;
        }).collect(Collectors.toList());
        recordDetailExtRepository.batchSave(detailExtDTOList);
    }

    /**
     * 更新订单拓展信息
     *
     * @param reportRequestDTO
     * @param testOrderDTO
     */
    private void updateOrderExtInfo(ReportRequestDTO reportRequestDTO, TestOrderDTO testOrderDTO) {
        TestOrderExtendsDTO orderExtendsDTO = new TestOrderExtendsDTO();
        ReportReqDTO reportReqDTO = reportRequestDTO.getReportReqDTO();
        orderExtendsDTO.setLatitude(reportReqDTO.getLatitude());
        orderExtendsDTO.setLongitude(reportReqDTO.getLongitude());
        orderExtendsDTO.setBriefAddress(reportReqDTO.getBriefAddress());
        orderExtendsDTO.setAddressDetail(reportReqDTO.getAddressDetail());
        orderExtendsDTO.setTestOrderNo(testOrderDTO.getTestOrderNo());
        orderExtendsRepository.updateByOrderNo(orderExtendsDTO, testOrderDTO.getTestOrderNo());
    }

}
